/**
 * bind 之后还能修改 this 指向吗？为什么？
 * 规范里是这么定义的，如果调用 call 的函数是一个 bound function, 那么定义的 this 会被忽略。
 */

var name = 'window'
let Person = {
	name: 'Tom',
	say(age, like) {
		console.log(`我叫${this.name},今年${age}岁,喜欢${like}`)
		return {
			age,
			like,
		}
	},
}

// 先看代码执行效果
Person1 = {
	name: 'Tom1',
}

// 我们尝试用原生方法call来实现this指向Person1
// Person.say.call(Person1) //我叫Tom1

/* ===========================================================  call  =============================================== */

// 自己定义的myCall
Function.prototype.myCall = function (context) {
	// 判断调用对象
	if (typeof this !== 'function') {
		console.error('type error')
	}

	// 判断 context 是否传入，如果未传入则设置为 globalThis
	context = context || globalThis

	// 将调用函数设为对象的方法,最好设置symbol避免覆盖fn
	context.fn = this

	// 获取参数
	const args = [...arguments].slice(1)

	// 调用函数
	const result = context.fn(...args)

	// 将属性删除
	delete context.fn

	return result
}

// Person.say.myCall(Person1, 10, 'xinxin')

/* ===========================================================  apply  =============================================== */

// 自己定义的myApply
Function.prototype.myApply = function (context) {
	// 判断调用对象是否为函数
	if (typeof this !== 'function') {
		throw new TypeError('Error')
	}

	// 判断 context 是否存在，如果未传入则为 globalThis
	context = context || globalThis

	// 将函数设为对象的方法
	context.fn = this

	// 调用方法 , 判断是否传入参数
	const result = arguments[1] ? context.fn(...arguments[1]) : context.fn()

	// 将属性删除
	delete context.fn

	return result
}

// Person.say.myApply(Person1, [20, 'leilei'])

/* ===========================================================  bind  =============================================== */
Function.prototype.myBind = function (context, ...args) {
	// 保存当前函数的引用
	const fn = this

	// 创建一个新的函数
	const boundFunction = function (...newArgs) {
		// 如果通过 `new` 关键字调用了这个函数
		if (this instanceof boundFunction) {
			return new fn(...args, ...newArgs)
		}
		// 否则按照正常的方式调用
		return fn.apply(context, [...args, ...newArgs])
	}

	// 让新函数能够继承原函数的原型
	if (fn.prototype) {
		boundFunction.prototype = Object.create(fn.prototype)
	}

	return boundFunction
}
// Person.say.myBind(Person1, 20)('leixin')

var fn = Person.say.myBind(Person1, 20)
var res = new fn('leixin')
console.log(res)

/* ===========================================================  softBind  =============================================== */
// this 可以多次改变
return
Function.prototype.softBind = function (obj, ...rest) {
	const fn = this
	const bound = function (...args) {
		const o = !this || this === (window || global) ? obj : this
		return fn.apply(o, [...rest, ...args])
	}

	bound.prototype = Object.create(fn.prototype)
	return bound
}
let obj = { name: 'obj' }
obj2 = { name: 'obj2' }
obj3 = { name: 'obj3' }
function foo() {
	console.log(this.name)
}
let fooBJ = foo.softBind(obj)
fooBJ() // name: obj   这个时候软绑定已经生效了，this绑定到obj上
obj2.foo = foo.softBind(obj)
obj2.foo() //name: obj2   这里已经的this隐式绑定到obj2上了
fooBJ.call(obj3) // name: obj3  这里this被硬绑定到obj3上了
setTimeout(obj2.foo, 100) // name: obj  软绑定了最初的obj
